import re
from datetime import datetime
from typing import List, Optional

import ninja
from django_redis import get_redis_connection
from ninja import Field
from ninja_extra import service_resolver
from ninja_extra.controllers import RouteContext
from ninja_extra.exceptions import APIException
from ninja_schema import Schema, ModelSchema
from pydantic import field_validator, model_validator
from pydantic_core.core_schema import ValidationInfo

from goods.models import SKU
from users.models import User, Address


class UserSchemaIn(ModelSchema):
    sms_code: int = None
    allow: bool = None
    password2: str

    class Config:
        model = User
        # exclude = ['id', 'created_at','last_login','first_name','last_name','email',]
        include = ['username', 'password', 'mobile', ]

    """
    check_fields=True：在类创建时检查字段是否存在，确保验证器的字段引用是正确的。
    check_fields=False：跳过字段存在性检查，适用于动态字段或子类中定义字段的场景
    """

    @field_validator("username", mode='after', check_fields=True)
    def check_username(cls, v):
        """校验用户名"""
        if User.objects.filter(username=v).exists():
            raise ValueError('用户名已存在，请重新输入')
        return v

    @field_validator("mobile", mode='after')
    def check_mobile(cls, v):
        """校验手机号"""
        if not re.match(r'1[3-9]\d{9}', v):
            raise ValueError('请输入正确的手机格式')
        return v

    @field_validator('allow', mode='after')
    def check_allow(cls, v):
        """用户协议"""
        if not v:
            raise ValueError('请同意用户协议')
        return v

    @model_validator(mode='after')
    def check_schema(cls, values, info: ValidationInfo):
        """
        model_validator 和 field_validator 都是before的情况下，model_validator高于field_validator
        mode：指定验证器的模式，可以是 'before'、'after' 或 'wrap'。
            'before'：在任何字段验证之前运行。   values类型为DJangoGetter：values.password
            'after'：在所有字段验证之后运行。    values类型为<class 'users.schema.UserSchemaIn'>
            'wrap'：包裹现有的验证器。          values类型为dict：values['password']
        """
        if User.objects.filter(mobile=values.mobile).exists():
            raise ValueError("手机号已注册")

        if len(values.password) < 6 or len(values.password) > 16:
            raise ValueError('密码长度最小6位，最大16位')

        forbidden_passwords = (
            info.context.get('forbidden_passwords', ['123', '123456']) if info.context else []
        )
        if values.password in forbidden_passwords:
            raise ValueError(f'Password {values.password} is forbidden.')

        if values.password != values.password2:
            raise ValueError('两次输入密码不一致，请重新输入')

        # 校验验证码
        redis_conn = get_redis_connection('verify_code')
        redis_sms_code = redis_conn.get(f'sms_{values.mobile}')
        # redis中存储的是byte类型(b'519210')，所以需要decode(),注意 if 判断的顺序
        # values.sms_code, redis_sms_code.decode()     <class 'int'> <class 'str'>
        if redis_sms_code is None or str(values.sms_code) != redis_sms_code.decode():
            raise ValueError('验证码错误，请重新输入')
        return values


class UserSchemaOut(ModelSchema):
    class Meta:
        model = User
        exclude = ['password', 'email']


# 用户登录请求字段
class LoginSchema(Schema):
    username: str = Field(..., description="用户名")
    password: str = Field(..., description="密码")


# 用于生成TOKEN的用户信息
class UserInfoSchema(ModelSchema):
    email: str = None  # 因为邮箱为空，直接序列化会报错：None - 标记使它非必填
    """
    ERROR 1 validation error for UserTokenOutSchema
    user.email
    value is not a valid email address: An email address must have an @-sign. [type=value_error, input_value='', input_type=str]
    """

    class Config:
        model = User
        include = ["id", "username", "mobile", "email", "email_active"]


# Token
class UserTokenOutSchema(Schema):
    token: str
    user: UserInfoSchema
    token_exp_date: Optional[datetime]


class UserUpdateSchema(ModelSchema):
    class Meta:
        model = User
        include = ['email']

    def update(self, obj):
        for v, k in self.dict().items():
            setattr(obj, v, k)
        obj.save()
        return obj


class AddressSchemaOut(ninja.ModelSchema):
    """
    使用ninja_schema中的ModelSchema时，如果自定义字段为必填项且设置别名时，在嵌套序列化的时候会报错，修改为选填则可正常序列化
    使用ninja的ModelSchema则不会，
        12 validation errors for AddressesSchemaOut
    addresses.0.`province.name`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C6890>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.0.`city.name`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C6890>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.0.`district.name`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C6890>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.0.`province.id`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C6890>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.0.`city.id`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C6890>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.0.`district.id`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C6890>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.1.`province.name`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C67F0>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.1.`city.name`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C67F0>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.1.`district.name`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C67F0>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.1.`province.id`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C67F0>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.1.`city.id`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C67F0>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    addresses.1.`district.id`
      Field required [type=missing, input_value=<ninja_schema.orm.getters...t at 0x000001B65C3C67F0>, input_type=DjangoGetter]
        For further information visit https://errors.pydantic.dev/2.10/v/missing
    """
    province: str = Field(..., alias="province.name")
    city: str = Field(..., alias="city.name")
    district: str = Field(..., alias="district.name")

    # id没有添加定义的话前端修改地址时省市区数据为空，类似的情况注意，如果在下面的字段中添加这三个id字段反而会报错
    province_id: int = Field(..., alias="province.id")
    city_id: int = Field(..., alias="city.id")
    district_id: int = Field(..., alias="district.id")

    class Meta:
        model = Address
        fields = ['id', 'title', 'receiver', 'province', 'city', 'district', 'place', 'mobile', 'tel', 'email', ]


# class AddressUpdateSchemaOut(ninja.Schema):
#     title: Optional[str] = None
#     receiver: Optional[str] = None
#
#     province: Optional[str] = None
#     city: Optional[str] = None
#     district: Optional[str] = None
#
#     place: Optional[str] = None
#     mobile: Optional[str] = None
#     tel: Optional[str] = None
#     email: Optional[str] = None


class AddressesSchemaOut(Schema):
    user_id: int
    default_address_id: Optional[int] = None
    limit: int = 20
    addresses: List[AddressSchemaOut]


class AddressSchemaIn(Schema):
    title: str
    receiver: str
    province_id: int
    city_id: int
    district_id: int
    place: str
    mobile: str
    tel: str = Field(None)
    email: str = Field(None)

    class Meta:
        model = Address
        exclude = ['id', 'user', 'is_deleted', 'created_at', 'updated_at']


class UserBrowseHistorySchemaIn(ninja.Schema):
    sku_id: int

    @model_validator(mode='after')
    def check_sku_id(cls, value):
        """
        model_validator 和 field_validator 都是before的情况下，model_validator高于field_validator
        mode：指定验证器的模式，可以是 'before'、'after' 或 'wrap'。
            'before'：在任何字段验证之前运行。   values类型为DJangoGetter：values.password
            'after'：在所有字段验证之后运行。    values类型为<class 'users.schema.UserSchemaIn'>
            'wrap'：包裹现有的验证器。          values类型为dict：values['password']
        """
        context: RouteContext = service_resolver(RouteContext)
        try:
            SKU.objects.get(id=value.sku_id)
        except SKU.DoesNotExist:
            raise APIException('数据有误或不存在，请重试')
        sku_id = value.sku_id
        user_id = context.request.user.id
        # 连接redis
        redis_conn = get_redis_connection('history')
        # 通过管道操作
        pl = redis_conn.pipeline()
        # 0 去掉所有重复的sku_id
        pl.lrem(f'history_{user_id}', 0, sku_id)
        # 添加到列表开头位置
        pl.lpush(f'history_{user_id}', sku_id)
        # 截取前5位--历史记录展示多少取多少
        pl.ltrim(f'history_{user_id}', 0, 4)
        # 执行
        pl.execute()
        return value


"""
ValueError: 
{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body",
        "data",
        "allow"
      ],
      "msg": "Value error, 请同意用户协议",
      "ctx": {
        "error": "请同意用户协议"
      }
    },    
}

from ninja_extra.exceptions import APIException
APIException:只显示一个错误信息
{
  "detail": "请输入正确的手机格式"
}

from ninja.errors import ValidationError
ValidationError([{'mobile':'请输入正确的手机格式'}])      
{
  "detail": [
    {
      "mobile": "请输入正确的手机格式"
    }
  ]
}


from ninja_jwt.exceptions import ValidationError
ValidationError('请输入正确的手机格式')
{
  "detail": "{'detail': ErrorDetail(string='请输入正确的手机格式', code='invalid'), 'code': ErrorDetail(string='invalid', code='invalid')}"
}


from ninja_extra.exceptions import ValidationError
{
  "detail": "[ErrorDetail(string='请输入正确的手机格式', code='invalid')]"
}
"""
